import collections

MaskedIPv4Value = collections.namedtuple('MaskedIPv4Value', ('address', 'netmask', 'cidr'))
SimpleValue = collections.namedtuple('SimpleValue', ('value'))

#Arbitrary constants used to support wildcard-like values in patterns
#All of these handle parsing and expose appropriate values.

def _bounded_identifier(identifier, min, max, implicit_cap=True):
    if not min and not max:
        if not implicit_cap:
            return identifier
        count = '...'
    else:
        count = '{min}:{max}'.format(
            min=min or '...',
            max=max or '...',
        )
        
    return '{identifier}[{count}]'.format(
        identifier=identifier,
        count=count,
    )
    
def _ipv4_to_octets(ipv4):
    octets = ipv4.split('.')
    if len(octets) != 4 or not all(o.isdigit() for o in octets):
        raise ValueError("{value} is not a dotted quad".format(
            value=value,
        ))
    octets = [int(o) for o in octets]
    if not all(0 <= o <= 255 for o in octets):
        raise ValueError("{value} is not a valid IPv4 address".format(
            value=value,
        ))
    return octets
    
def _cidr_to_netmask(cidr):
    value = (0xFFFFFFFF << (32 - cidr)) & 0xFFFFFFFF
    return '.'.join(str(o) for o in (
        value >> 24 & 0xFF,
        value >> 16 & 0xFF,
        value >> 8 & 0xFF,
        value & 0xFF,
    ))
    
def _netmask_to_cidr(netmask):
    cidr = 0
    for octet in _ipv4_to_octets(netmask):
        if octet == 255:
            cidr += 8
        else:
            for i in xrange(8):
                if octet >> i & 0b1:
                    cidr += 8 - i
                    break
            break
    return cidr
    
class Variable(object):
    identifier = "none"
    
    def parse(self, value):
        raise NotImplementedError("parse() must be implemented by subclasses")

class Boolean(Variable):
    identifier = "boolean"
    
    _on = ('on', '1', 'y', 'yes', 'true', 'enable',)
    _off = ('off', '0', 'n', 'no', 'false', 'disable',)
    
    def __init__(self, on=None, off=None):
        if on:
            self._on = on
        if off:
            self._off = off
            
    def parse(self, value):
        v = value.lower()
        if v in self._on:
            return SimpleValue(True)
        elif v in self._off:
            return SimpleValue(False)
        raise ValueError("{value} is not recognised".format(
            value=value,
        ))
        
class Number(Variable):
    def __init__(self, min=None, max=None):
        self._min = min
        self._max = max
        
    def _validate(self, value):
        if ((self._min is not None and value < self._min) or
            (self._max is not None and value > self._max)):
            raise ValueError("{value} is not between {min} and {max}".format(
                value=value,
                min=self._min or '<undefined>',
                max=self._max or '<undefined>',
            ))
            
class Integer(Number):
    identifier = "integer"
    
    def parse(self, value):
        value = int(value)
        self._validate(value)
        return SimpleValue(value)
        
class Float(Number):
    identifier = "float"
    
    def parse(self, value):
        value = float(value)
        self.validate(value)
        return SimpleValue(value)
        
class String(Variable):
    identifier = "string"
    
    def __init__(self, min=None, max=None):
        self._min = min
        self._max = max
    
    @property
    def identifier(self):
        return _bounded_identifier("string", self._min, self._max, implicit_cap=False)
        
    def _validate(self, value):
        if ((self._min is not None and len(value) < self._min) or
            (self._max is not None and len(value) > self._max)):
            raise ValueError("{value} is not between {min} and {max} characters".format(
                value=value,
                min=self._min or '<undefined>',
                max=self._max or '<undefined>',
            ))
            
    def parse(self, value):
        self._validate(value)
        return SimpleValue(value)
        
class IPv4(Variable):
    identifier = "ipv4"
    
    def parse(self, value):
        return SimpleValue('.'.join(str(int(o)) for o in _ipv4_to_octets(value)))
        
class MaskedIPv4(IPv4):
    identifier = "ipv4-with-mask"
    
    def __init__(self, default_cidr=None):
        self._default_cidr=default_cidr
        
    def parse(self, value):
        parts = value.split('/', 1)
        if len(parts) == 1 and self._default_cidr is not None:
            address = parts[0]
            prefix = self._default_cidr
        elif len(parts) != 2:
            raise ValueError('{value} is not in "address/prefix" form'.format(
                value=value,
            ))
        else:
            (address, prefix) = parts
            
        address = IPv4.parse(self, address).value
        try:
            cidr = int(prefix)
        except ValueError:
            netmask = IPv4.parse(self, prefix).value
            cidr = _netmask_to_cidr(netmask)
        else:
            if not 0 <= cidr <= 32:
                raise ValueError("{cidr} is an invalid CIDR value".format(
                    cidr=cidr,
                ))
            netmask = _cidr_to_netmask(cidr)
        return MaskedIPv4Value(address, netmask, cidr)
        
class IPv6(Variable):
    identifier = "ipv6"
    
class VariableList(object):
    _variable = None
    
    def __init__(self, variable, min=None, max=None):
        self._variable = variable
        self._min = min
        self._max = max
        
    @property
    def min(self):
        return self._min
        
    @property
    def max(self):
        return self._max
        
    @property
    def identifier(self):
        return _bounded_identifier(self._variable.identifier, self._min, self._max)
        
    @property
    def variable(self):
        return self._variable
        
    def step_sequence(self):
        """
        Used for evaluation, this assumes that an instance has been traversed to calculate bounds.
        """
        if self._min:
            min = self._min - 1
        else:
            min = self._min
            
        if self._max:
            max = self._max - 1
        elif self._max == 0:
            raise ValueError("Too many elements of type {identifier}".format(
                identifier=self._variable.identifier,
            ))
        else:
            max = self._max
            
        return VariableList(self._variable, min, max)
        